#include "lib_fs_block_l3.h"
#include <stdio.h>

#include "lib_fs_block_l3.private"

#define iblock_root    1

OS_FILE_t *OS_fopen
(
    char_t name[],
    char_t mode[]
)
{
    OS_FILE_t      *file;
    OS_FileEntry_t fileEntry;
    OS_FILE_t      dir;
    char_t         filename[FILE_NAME_SIZE]; //Name without folders
    sint32_t       rc;

    //printf("OS_fopen(%s)\n", name);

    if (rootFileEntry.iblock == 0)
    {
        deadbeaf("fOpen does't need to remount the file system, but rootFileEntry.iblock=0");
    }

    file = (OS_FILE_t *) my_malloc(sizeof(OS_FILE_t));
    if (file == NULL)
    {
        return NULL;
    }
    //OS_MutexPend(mutexFilesys);
    if ((name[0] == '\0')logicalOr(strcmp(name, "/") == 0))
    {
        FileOpen(file, NULL, NULL);
        //OS_MutexPost(mutexFilesys);
        return file;
    }
    if (mode[0] == 'w')
    {
        //Don't over write a directory
        fileEntry.isDirectory = 0;
        rc                    = FileFindRecursive(&dir, name, &fileEntry, filename);
        if (rc == 0)
        {
            if (fileEntry.isDirectory)
            {
                printf("f_open, don't over write a directory\n");
                MediaFilRelease(file);
                return NULL;
            }
            OS_fdelete(name);
        }
    }
    rc = FileFindRecursive(&dir, name, &fileEntry, filename);
    if ((rc == -2)logicalOr((rc!=0) logicalAnd (mode[0] == 'r')))
    {
        //printf("L3: f_open, point2\n");
        MediaFilRelease(file);
        //OS_MutexPost(mutexFilesys);
        return NULL;
    }
    if (rc)
    {
        fileEntry.valid = 0;
    }
    rc                = FileOpen(file, filename, &fileEntry); //Open file
    file->fullname[0] = '\0';
    strncat(file->fullname, name, FULL_NAME_SIZE);
    //OS_MutexPost(mutexFilesys);
    if (mode[0] == 'a')
    {
        OS_fseek(file, 0, 2);  //goto end of file
    }
    return file;
}


void OS_fclose
(
    OS_FILE_t *file
)
{
    OS_FileEntry_t fileEntry;
    OS_FILE_t      dir;
    char_t         filename[FILE_NAME_SIZE];
    sint32_t       rc;

    if (file == NULL)
    {
        printf("OS_fclose: NULL file\n");
        return;
    }

    if (file->fileModified)
    {
        // Write file->fileEntry into parent directory
        //OS_MutexPend(mutexFilesys);
        BlockFlush(file);
        rc = FileFindRecursive(&dir, file->fullname, &fileEntry, filename);
        if ((file->fileEntry.mediaType == FILE_MEDIA_FLASH) logicalAnd (rc == 0))
        {
            // Invalidate old entry and add new entry at the end
            fileEntry.valid = 0;
            OS_fwrite(&fileEntry, sizeof(OS_FileEntry_t), 1, &dir);
            FileFind(&dir, "endoffile", &fileEntry);
        }
        OS_fwrite(&file->fileEntry, sizeof(OS_FileEntry_t), 1, &dir);
        BlockFlush(&dir);
        //OS_MutexPost(mutexFilesys);
    }
    MediaFilRelease(file);
}

int OS_fmkdir(char_t name[])
{
    OS_FILE_t *file;
    sint32_t  ans;

printf("mkdir %s\n", name);

    file = OS_fopen(name, "w+");
    if (file == NULL)
    {
        ans = -1;
    }
    else
    {
        file->fileEntry.isDirectory = 1;
        OS_fclose(file);
        ans = 0;
    }
    return ans;
}


void OS_fdelete
(
    char_t name[]
)
{
    OS_FILE_t      dir;
    OS_FILE_t      file;
    OS_FileEntry_t fileEntry;
    sint32_t       rc;
    uint32_t       iblock;
    char_t         filename[FILE_NAME_SIZE]; //Name without directories

    //OS_MutexPend(mutexFilesys);
    rc = FileFindRecursive(&dir, name, &fileEntry, filename);
    if (rc == 0)
    {
        /*
         * cleaning
         */
        FileOpen(&file, NULL, &fileEntry);
        for (iblock = file.iblock; file.block->next != BLOCK_EOF; iblock = file.iblock)
        {
            BlockRead(&file, file.block->next);
            MediaBlockUnAlloc(&file, iblock);
        }
        MediaBlockUnAlloc(&file, iblock);

        my_memset(fileEntry.name, 0x0, FILE_NAME_SIZE - 1);
        //strncpy(fileEntry.name, "deadbeaf", FILE_NAME_SIZE - 1);
        fileEntry.valid = 0;
        OS_fwrite((char *) &fileEntry, sizeof(OS_FileEntry_t), 1, &dir);
        BlockFlush(&dir);
    }
    //OS_MutexPost(mutexFilesys);
}

private uint8_t uc_get_8
(
    uint8_t buffer[],
    uint32_t i
)
{
    return buffer[i];
}

boolean_t OS_file_is_existing
(
    char_t name[]
)
{
    boolean_t      ans;

    OS_FILE_t      dir;
    OS_FileEntry_t fileEntry;
    char_t         filename[FILE_NAME_SIZE]; /* Name without directories */
    sint32_t       rc;
    uint32_t       iblock;
    uint32_t       ientry;

    //OS_MutexPend(mutexFilesys);
    rc = FileFindRecursive(&dir, name, &fileEntry, filename);
    if (rc == 0)
    {
        printf("############### [%s]: found ###################\n", name);
        iblock = dir.iblock;
        ientry = dir.blockOffset / sizeof(fileEntry);
        ans    = True;
    }
    else
    {
        printf("############### [%s]: not found ###############\n", name);
        ans = False;
    }
    //OS_MutexPost(mutexFilesys);

    return ans;
}

boolean_t OS_frename
(
    char_t name[],
    char_t name_new[]
)
{
    boolean_t      ans;
    OS_FILE_t      dir;
    OS_FileEntry_t fileEntry;
    sint32_t       rc;
    sint32_t       rc_new;
    uint32_t       iblock;
    char_t         filename[FILE_NAME_SIZE]; //Name without directories
    uint32_t       offset;
    uint32_t       i;

    ans = False;

    //OS_MutexPend(mutexFilesys);

    /*
     * is file name_new alreay existing
     */
    rc_new = FileFindRecursive(&dir, name_new, &fileEntry, filename);
    if (rc_new != 0)
    {
        rc = FileFindRecursive(&dir, name, &fileEntry, filename);
        if (rc == 0)
        {
            //printf("############### [%s] found ###############\n", name);
            //printf("[%s]\n", fileEntry.name); // file
            //printf("[%s]\n", dir.fileEntry.name); //root

            /*
             * dirty tricks
             */
            offset = dir.fileOffset - sizeof(fileEntry);

            my_memset(fileEntry.name, 0x0, FILE_NAME_SIZE - 1);
            strncpy(fileEntry.name, name_new, FILE_NAME_SIZE - 1);

        #ifdef _dirty_
            for (i = 0; i < sizeof(fileEntry); i++)
            {
                dir.block->data[offset + i] = uc_get_8((p_uint8_t) &fileEntry, i);
                //printf("%c\n", dir.block->data[offset+i]);
            }
            dir.blockModified = True;
            BlockFlush(&dir);
            //printf("iblock=%lx\n", dir.iblock);
            MediaBlockWrite(&dir, dir.iblock);
        #else
            dir.blockModified = True;
            OS_fwrite((p_uint8_t) &fileEntry, sizeof(OS_FileEntry_t), 1, &dir);
            BlockFlush(&dir);
        #endif

            ans = True;
        }
        else
        {
            //printf("############### [%s]: not found ###############\n", name);
        }
    }

    /*
     * useful ideas
     *
     * fileEntry.valid = True;
     * OS_fwrite((p_uint8_t) &fileEntry, sizeof(OS_FileEntry_t), 1, &dir);
     * dir.fileOffset=offset;
     */

    //OS_MutexPost(mutexFilesys);
    return ans;
}

sint32_t OS_fdir
(
    OS_FILE_t *dir,
    char_t name[]
)
{
    sint32_t       ans;
    OS_FileEntry_t *fileEntry;
    uint32_t       count;

    fileEntry = (OS_FileEntry_t *) name;
    if (dir == NULL)
    {
        printf("dir NULL\n");
        ans = -1;
    }
    else
    {
        while (True)
        {
            count = OS_fread(fileEntry, sizeof(OS_FileEntry_t), 1, dir);
            if ((count == 0) logicalOr (fileEntry->iblock == BLOCK_EOF))
            {
                return -1;
            }
            if (fileEntry->valid == 1)
            {
                break;
            }
        }
        ans = 0;
    }
    return ans;
}

/* ---------------------------------------- */
sint32_t DirRecursive
(
    char_t name[]
)
{
    OS_FileEntry_t fileEntry;
    OS_FILE_t      *dir;
    char_t         fullname[FULL_NAME_SIZE];
    sint32_t       rc;

    dir = OS_fopen(name, "r");
    if (dir == NULL)
    {
        return 0;
    }
    while (True)
    {
        rc = OS_fdir(dir, (char *) &fileEntry);
        if (rc)
        {
            break;
        }
        printf("%8ld", fileEntry.length);
        printf(" ");
        printf("[%s]", fileEntry.name);
        printf("\n");
        if (fileEntry.isDirectory)
        {
            if (strcmp(name, "/") == 0)
            {
                sprintf(fullname, "/%s", fileEntry.name);
            }
            else
            {
                sprintf(fullname, "%s/%s", name, fileEntry.name);
            }
            DirRecursive(fullname);
        }
    }
    OS_fclose(dir);
    return 0;
}

/*
 * invoked by mkfs
 * in order to allocate the root
 * in the correct way
 */

private boolean_t OS_my_mkfs_bless()
{
    OS_FILE_t dir;
    boolean_t ans;
    uint32_t  iblock;

    printf("mkfs file system bless\n");

    disk_bitmap_load();

    //mutexFilesys = OS_MutexCreate("filesys");
    iblock = iblock_root;
    disk_cb_read((p_uint8_t) &dir, iblock, sizeof(OS_FileEntry_t));

    ans = False;

    printf(" +dir.fileEntry.blockSize=%d\n", dir.fileEntry.blockSize);
    printf(" +dir.fileEntry.iblock = %ld\n", dir.fileEntry.iblock);

    strcpy(rootFileEntry.name, dir.fileEntry.name);
    rootFileEntry.mediaType   = dir.fileEntry.mediaType;
    rootFileEntry.blockSize   = dir.fileEntry.blockSize;
    rootFileEntry.iblock      = dir.fileEntry.iblock;
    rootFileEntry.isDirectory = True;

    printf(" +rootFileEntry.iblock = %ld\n", rootFileEntry.iblock);
    printf(" +rootFileEntry.name   = [%s]\n", dir.fileEntry.name);

    iblock = rootFileEntry.iblock;
    BlockRead(&dir, BLOCK_ALLOC);
    BlockFlush(&dir);

    printf(" +RootFileEntry has been allocated at iblock=%ld\n", iblock);
    printf(" +mounted\n");

    //OS_MutexPost(mutexFilesys);

    ans = True;

    return ans;
}

boolean_t OS_my_mount()
{
    OS_FILE_t dir;
    boolean_t ans;
    uint32_t  iblock;

    printf("Mount file system, ");

    disk_bitmap_load();


    //mutexFilesys = OS_MutexCreate("filesys");
    iblock = iblock_root;
    disk_cb_read((p_uint8_t) &dir, iblock, sizeof(OS_FileEntry_t));

    ans = False;
    switch (dir.fileEntry.mediaType)
    {
    case    FILE_MEDIA_RAM:
        printf("MEDIA_RAM ");
        break;
    case FILE_MEDIA_FLASH:
        printf("MEDIA_FLASH (not supported) ");
        break;
    case FILE_MEDIA_DISK:
        printf("MEDIA_DISK ");
        break;
    default:
        printf("MEDIA_UNKNOWN ");
        break;
    }
    printf("\n");

    printf(" +dir.fileEntry.blockSize=%d\n", dir.fileEntry.blockSize);
    printf(" +dir.fileEntry.iblock = %ld\n", dir.fileEntry.iblock);

    strcpy(rootFileEntry.name, dir.fileEntry.name);
    rootFileEntry.mediaType   = dir.fileEntry.mediaType;
    rootFileEntry.blockSize   = dir.fileEntry.blockSize;
    rootFileEntry.iblock      = dir.fileEntry.iblock;
    rootFileEntry.isDirectory = True;

    printf(" +rootFileEntry.iblock = %ld\n", rootFileEntry.iblock);
    printf(" +rootFileEntry.name       = [%s]\n", dir.fileEntry.name);
    printf(" +mounted\n");

    /*
     * in case you need
     * iblock = rootFileEntry.iblock;
     */

    //OS_MutexPost(mutexFilesys);

    ans = True;
    return ans;
}



boolean_t OS_my_mkfs
(
    uint8_t mediaType
)
{
    OS_FILE_t *file;
    OS_FILE_t dir;
    boolean_t ans;
    uint32_t  iblock;

    if (mediaType == FILE_MEDIA_DISK)
    {
        printf("mkfs file system, ");
        //mutexFilesys = OS_MutexCreate("filesys");

        file = &dir;

        for (iblock = 0; iblock < DISK_BLOCK_N; iblock++)
        {
            MediaBlockUnAlloc_disk(NULL, iblock);
        }
        //disk_bitmap_set_free_all_blocks();



        /* step0 */
        /*
         * if '#' insteaf of 0x0 it causes a crash
         */
        my_memset(&dir, 0x0, sizeof(OS_FILE_t));

        /* step1 */
        iblock = iblock_root;
        disk_bitmap_set_used_block(iblock);
        iblock = disk_bitmap_get_free_block();
        disk_bitmap_set_used_block(iblock);

        /* step2 */
        dir.fileEntry.mediaType = mediaType;
        dir.fileEntry.blockSize = BLOCK_SIZE;
        dir.fileEntry.iblock    = iblock;
        strcpy(dir.fileEntry.name, "/");
        dir.blockModified = False;

        /* step3 */
        iblock = iblock_root;
        disk_cb_write((p_uint8_t) &(dir.fileEntry), iblock, sizeof(OS_FileEntry_t));

        /* step4 */
        ans = OS_my_mkfs_bless();

        printf("done\n");
    }
    //OS_MutexPost(mutexFilesys);
    return ans;
}
